//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 27/09/2012.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog.  If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include "EdgeDetector.h"
#include "Frame.h"
#include "Spectrum.h"
#include <cmath>

LOG_DEFINE(EdgeDetector);

EdgeDetector::EdgeDetector(IEventListener<EdgeDetector::Event>& listener,
                           std::unique_ptr<std::ostream> debug)
: _listener(listener),
  _debug(std::move(debug)),
  _ema_level(new ExponentialMovingAverage<float>(0.02f)),
  _fallen_since_last_event(true),
  _detected(false),
  _frame_count(0),
  _fall_threshold_ema_factor(0.75f),
  _rise_threshold_ema_factor(1.3f),
  _fall_last_peak_factor(0.38f),
  _last_peak(0.f),
  _last_level(0.f),
  _last_rise_threshold(0.f)
{}


EdgeDetector::~EdgeDetector() {}


void EdgeDetector::submitFrame(const Frame& frame) {
    for( auto half : frame.halves() )
    {
        for( auto quarter : half.halves() )
        {
            reallySubmitFrame(quarter);
        }
    }
}


void EdgeDetector::reallySubmitFrame(const Frame& frame) {
    const float level = frame.spectrum().rms();
    const float timestamp = frame.endTimestamp();
    const float ema = _ema_level->output(level);
    const float fall_threshold_ema = ema * _fall_threshold_ema_factor;
    const float fall_threshold_peak = _last_peak * _fall_last_peak_factor;
    const float fall_threshold = std::max(fall_threshold_ema,
                                          fall_threshold_peak);
    const float rise_threshold_ema = ema*_rise_threshold_ema_factor;
    const float min_dist_above_fall_threshold = 1.2f*fall_threshold;
    const float rise_threshold = std::max(rise_threshold_ema,
                                          std::max(min_dist_above_fall_threshold,
                                                   0.9f*_last_rise_threshold));
    const bool in_fallen_zone = level < fall_threshold;
    const bool in_risen_zone = level > rise_threshold;
    const bool higher_than_last_frame = level > _last_level;
    
    if(in_fallen_zone)
    {
        _fallen_since_last_event = true;
        _last_peak = 0.f;
    }
    else
    {
        _last_peak = std::max(level, _last_peak);
        if(_fallen_since_last_event && in_risen_zone && higher_than_last_frame)
        {
            _fallen_since_last_event = false;
            Event evt(timestamp);
            LOG_INFO("EdgeDetector::submitFrame(...) sending " << evt);
            _listener.onEvent(evt);
            _fallen_since_last_event = false;
        }
    }
    
    if(_debug)
    {
        *_debug << timestamp
               << " " << level
               << " " << ema
               << " " << in_fallen_zone
               << " " << fall_threshold
               << " " << (_fallen_since_last_event ? 1 : 0)
               << " " << rise_threshold
               << " " << _last_peak
               << std::endl;
    }
    
    _last_level = level;
    _last_rise_threshold = rise_threshold;
}


bool EdgeDetector::detected() const { return _detected; }


EdgeDetector::Event::Event(float time_stamp)
: _time_stamp(time_stamp)
{}


bool EdgeDetector::Event::operator<(const Event& rhs) const
{
    return _time_stamp < rhs._time_stamp;
}


float EdgeDetector::Event::getTimeStamp() const
{
    return _time_stamp;
}


bool EdgeDetector::Event::approxEqual(const Event& rhs) const
{
    return fabs(_time_stamp - rhs._time_stamp) < 0.07f;
}


std::ostream& operator<<(std::ostream& os, const EdgeDetector& rhs)
{
    os << "EdgeDetector{}";
    return os;
}


std::ostream& operator<<(std::ostream& os, const EdgeDetector::Event& rhs)
{
    os << "EdgeDetector::Event{" << rhs.getTimeStamp() << "}";
    return os;
}